module serverlist;

private {
	import indigo.tools.linkedlist;
	import main;
	import common;
	import servertable;
}

const char[][] defaultGameTypes = ["FFA", "1v1", "SP", "TDM", "CTF"/*, "OFCTF", "Overload", "Harvester"*/];

char[][][char[]] gameTypes;

static this() {
	gameTypes["osp"] = split("FFA 1v1 SP TDM CTF CA");
	gameTypes["q3ut3"] = split("FFA FFA FFA TDM TS FtL C&H CTF B&D");
	gameTypes["westernq3"] = split("FFA Duel 2 TDM RTP BR");
	gameTypes["wop"] = split("FFA 1v1 2 SyC LPS TDM 6 SyCT BB");
}


// should correspond to playertable.playerHeaders
enum PlayerColumn { NAME, SCORE, PING };
// should correspond to servertable.serverHeaders
enum ServerColumn { NAME, PING, PLAYERS, GAMETYPE, MAP, ADDRESS };


private Thread serverThread;

struct ServerData {
	// name, ping, playercount, map, etc.
	char[][] server;
	// list of players, with name, score and ping for each
	char[][][] players;
	// list of cvars, with key and value for each
	char[][][] cvars;

	int opCmp(ServerData other) {
		switch (serverList.sortColumn) {
			case ServerColumn.PLAYERS:
				int a, b;

				a = humanCount;
				b = other.humanCount;
				assert (a >= 0 && b >= 0);
				if (a || b)	return b - a;

				a = botCount;
				b = other.botCount;
				assert (a >= 0 && b >= 0);
				if (a || b) return b - a;

				a = maxClients;
				b = other.maxClients;
				assert (a >= 0 && b >= 0);
				if (a || b) return b - a;

				return 0;
			case ServerColumn.PING:
				return std.conv.toInt(server[serverList.sortColumn]) - std.conv.toInt(other.server[serverList.sortColumn]);
			default:
				return std.string.icmp(server[serverList.sortColumn], other.server[serverList.sortColumn]);
		}
	}

	int humanCount() {
		char[] s = server[ServerColumn.PLAYERS];
		return std.conv.toInt(s[0..find(s, '+')]);
	}

	int botCount() {
		char[] s = server[ServerColumn.PLAYERS];
		return std.conv.toInt(s[ find(s, '+')+1 .. find(s, "/") ]);
	}

	int maxClients() {
		char[] s = server[ServerColumn.PLAYERS];
		return std.conv.toInt(s[ find(s, "/")+1 .. length ]);
	}

	bool hasHumans() { return server[ServerColumn.PLAYERS][0] != '0'; }

	bool hasBots() {
		char[] s = server[ServerColumn.PLAYERS];
		return (s[ find(s, '+')+1 ] != '0');
	}
}


class ServerList {
	bool reversed = false;

	synchronized {
		void insertSorted(ServerData sd) {
			list.append(sd);
			list.Iterator it = list.end;
			it--;

			if (!isFilteredOut(sd))
				_insertSorted(it.ptr);
		}

		int opApply(int delegate(inout ServerData) dg) {
			int result = 0;

			foreach(ServerData sd; list) {
				result = dg(sd);
				if (result)
					break;
			}
			return result;
		}

		void opCatAssign(ServerData server) {
			list ~= server;
			list.Iterator it = list.end;
			it--;
			filteredList ~= it.ptr;
		}

		/// Return a server from the filtered list
		ServerData getFiltered(int i) {
			return *filteredList[i];
		}

		size_t filteredLength() { return filteredList.length; }
		size_t length() { return list.length; }
		ServerList clear() { filteredList.length = 0; list.clear(); return this; }
		void sort() {
			debug Timer timer = new Timer;

			if (sortColumn_ != oldSortColumn_)
				list.sort();

			if (reversed)
				list.reverse();

			updateFilteredList();

			debug log("ServerList.sort took " ~ std.string.toString(timer.millis) ~ " milliseconds.");
		}


		/****** FILTERS ********/
		void filterNotEmpty(bool enable) {
			if (enable == filters_.notEmpty)
				return;

			filters_.notEmpty = enable;
			updateFilteredList();
			display.asyncExec(null, &serverTable.reset);
		}

		void filterHasHumans(bool enable) {
			if (enable == filters_.hasHumans)
				return;

			filters_.hasHumans = enable;
			updateFilteredList();
			display.asyncExec(null, &serverTable.reset);
		}
	}

	void sortColumn(int col) {
		assert(col >= 0 && col <= ServerColumn.max);
		oldSortColumn_ = sortColumn_;
		sortColumn_ = col;
	}

	int sortColumn() { return sortColumn_; }

private:
	LinkedList!(ServerData) list;
	ServerData*[] filteredList;

	int sortColumn_ = ServerColumn.NAME;
	int oldSortColumn_ = -1;

	struct Filters {
		bool notEmpty = false;
		bool hasHumans = false;
	};
	Filters filters_;

	synchronized {
		invariant {
			assert(filteredList.length <= list.length);
			assert(filters_.hasHumans || filters_.notEmpty ||
			       filteredList.length == list.length ||
			       filteredList.length == (list.length - 1));
		}

		void appendToFiltered(ServerData* psd) {
			filteredList ~= psd;
			display.asyncExec(null, &serverTable.update);
		}

		void _insertSorted(ServerData* sd)
		{
			bool less(ServerData* a, ServerData* b) {
				return reversed ? (*a > *b) : (*a < *b);
			}

			bool greaterOrEq(ServerData* a, ServerData* b) {
				return reversed ? (*a <= *b) : (*a >= *b);
			}

			if (filteredList.length == 0) {
				appendToFiltered(sd);
			}
			else if (filteredList.length == 1) {
				if (less(sd, filteredList[0])) {
					insert(0, sd);
				}
				else {
					appendToFiltered(sd);
				}
			}
			else {
				int i = filteredList.length / 2;
				int delta = i;
				if (delta < 1) delta = 1;
				for (;;) {
					if (delta > 1) delta /= 2;
					if (less(sd, filteredList[i])) {
						if (i == 0) {
							insert(0, sd);
							break;
						}
						else if (greaterOrEq(sd, filteredList[i-1])) {
							insert(i, sd);
							break;
						}
						else {
							i -= delta;
						}
					}
					else {
						assert(i <= filteredList.length);
						if (i == filteredList.length - 1) {
							appendToFiltered(sd);
							break;
						}
						else if (i == filteredList.length - 2) {
							if (greaterOrEq(sd, filteredList[i])) appendToFiltered(sd);
							else insert(i, sd);
							break;
						}
						else {
							i += delta;
						}
					}
				}
			}
		}

		void insert(size_t index, ServerData* sd)
		{
			assert (index < filteredList.length);
			filteredList = filteredList[0..index] ~ sd ~ filteredList[index..length];

			if (refreshWhenInserting)
				display.asyncExec(null, &serverTable.refresh);
		}

		void copyListToFilteredList()
		{
			int i = 0;
			list.Iterator begin, end;

			filteredList.length = 0;
			filteredList.length = list.length;

			for (list.Iterator it = list.begin(); it != list.end(); it++)
				filteredList[i++] = it.ptr;
		}

		bool isFilteredOut(ServerData sd) {
			if (!(filters_.hasHumans || filters_.notEmpty))
				return false;
			if (filters_.hasHumans && !sd.hasHumans)
				return true;
			if (filters_.notEmpty && !(sd.hasHumans || sd.hasBots))
				return true;

			return false;
		}

		void updateFilteredList()
		{
			list.Iterator begin, end;

			filteredList.length = 0;
			if (filters_.hasHumans) {
				for (list.Iterator it = list.begin(); it != list.end(); it++) {
					if (it.value.hasHumans) {
						filteredList ~= it.ptr;
					}
				}
			}
			else if (filters_.notEmpty) {
				for (list.Iterator it = list.begin(); it != list.end(); it++) {
					if (it.value.hasBots || it.value.hasHumans) {
						filteredList ~= it.ptr;
					}
				}
			}
			else {
				copyListToFilteredList();
			}
		}

		debug void printFiltered() {
			writefln("printFiltered(): ", filteredList.length, " elements in filteredList.");
			foreach (i, ServerData* psd; filteredList) {
				writefln(i, ": ", psd.server[ServerColumn.NAME]);
			}
			writefln();
		}

		debug void printList() {
			writefln("printList(): ", list.length, " elements in full list.");
			int i = 0;
			foreach (ServerData sd; list) {
				writefln(i++, ": ", sd.server[ServerColumn.NAME]);
			}
			writefln();
		}

	}
}


void loadSavedList()
{
	int f()
	{
		void callback(Object int_count) {
			statusBar.setLeft("Loading saved server list... " ~ std.string.toString((cast(Integer)int_count).value));
		}

		try {
			browserLoadSavedList(&callback);
			serverList.sort();
			if (!parselist.abort) {
				/*display.asyncExec(null, delegate void (Object o) {
				                            serverTable.reset();
				                        } );*/
			}
		}
		catch(Exception e) {
			logx(__FILE__, __LINE__, e);
		}
		return 0;
	}

	statusBar.setLeft("Loading saved server list...");
	serverThread = new Thread(&f);
	serverThread.start();
}

void getNewList()
{
	int f()
	{
		int serverCount = -1;
		static char[] total;

		void status(Object int_count) {
			statusBar.setLeft("Got " ~  total ~ " servers, querying..." ~ std.string.toString((cast(Integer)int_count).value));
		}

		try {
			serverCount = browserGetNewList();
			total = std.string.toString(serverCount);
			writefln("serverCount = ", total);

			if (serverCount >= 0) {
				display.asyncExec(null, delegate void (Object o) {
				                        statusBar.setLeft("Got "  ~
				                              total ~
				                              " servers, querying...");
			                        } );
				browserRefreshList(&status);
				serverList.sort();
				if (!parselist.abort)
					display.asyncExec(null, delegate void (Object o) {
					                            serverTable.reset();
					                        } );
			}

			qstat.saveRefreshList();

		}
		catch(Exception e) {
			writefln("getNewList: " ~ e.toString());
			logx(__FILE__, __LINE__, e);
		}
		return 0;
	}

	if (serverThread && serverThread.getState() != Thread.TS.TERMINATED) {
		if (!killServerBrowser()) {
			return;
		}
		if (serverThread) {
			log("waiting for serverThread...");
			try	serverThread.wait();
			catch (Object o) writefln(o.toString());
		}
		else {
			return;
		}
	}

	serverList.clear();
	serverTable.refresh();
	statusBar.setLeft("Getting new server list...");
	serverThread = new Thread(&f);
	serverThread.start();
}

void refreshList()
{
	int f()
	{
		static char[] total;

		void status(Object int_count) {
			statusBar.setLeft("Refreshing " ~  total ~ " servers..." ~ std.string.toString((cast(Integer)int_count).value));
		}

		void done(Object o)	{
			char[] s;
			if (serverList.length() > 0) {
				serverTable.reset();
			}
			else {
				s = "Nothing to refresh";
				statusBar.setLeft(s);
			}
		}

		try {
			total = std.string.toString(countServersInRefreshList());
			browserRefreshList(&status);
			serverList.sort();
			if (!parselist.abort)
				display.syncExec(null, &done);
		}
		catch(Exception e) {
			logx(__FILE__, __LINE__, e);
		}
		return 0;
	}

	if (serverThread && serverThread.getState() != Thread.TS.TERMINATED) {
		if (!killServerBrowser()) {
			return;
		}
		if (serverThread) {
			log("waiting for serverThread...");
			try	serverThread.wait();
			catch (Exception e) logx(__FILE__, __LINE__, e);
		}
		else {
			return;
		}
	}

	if (!exists(REFRESHFILE))
		qstat.saveRefreshList();
	statusBar.setLeft("Refreshing " ~  std.string.toString(countServersInRefreshList()) ~ " servers...");
	serverList.clear();
	serverTable.refresh();
	serverThread = new Thread(&f);
	serverThread.start();
}
